package com.wontube.client.extension.io.protocol.php;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author yuechao
 *
 */
public class AssocArray implements Cloneable, Serializable {
	private static final long serialVersionUID = -1510175151240724309L;

	private ArrayList<Object> arrayList;
	private LinkedHashMap<Object, Object> hashMap;
	private int arrayLength;
	private int maxNumber;

	public AssocArray() {
		arrayList = new ArrayList<Object>();
		hashMap = new LinkedHashMap<Object, Object>();
		arrayLength = 0;
		maxNumber = -1;
	}

	public AssocArray(int initialCapacity) {
		arrayList = new ArrayList<Object>(initialCapacity);
		hashMap = new LinkedHashMap<Object, Object>(initialCapacity);
		arrayLength = 0;
		maxNumber = -1;
	}

	public AssocArray(int initialCapacity, float loadFactor) {
		arrayList = new ArrayList<Object>(initialCapacity);
		hashMap = new LinkedHashMap<Object, Object>(initialCapacity, loadFactor);
		arrayLength = 0;
		maxNumber = -1;
	}

	public AssocArray(Collection<Object> c) {
		arrayList = new ArrayList<Object>(c);
		arrayLength = arrayList.size();
		maxNumber = arrayLength - 1;
		hashMap = new LinkedHashMap<Object, Object>(arrayLength);
		for (int i = 0; i < arrayLength; i++) {
			hashMap.put(new Integer(i), arrayList.get(i));
		}
	}

	public AssocArray(Map<Object, Object> m) {
		int len = m.size();
		arrayList = new ArrayList<Object>(len);
		hashMap = new LinkedHashMap<Object, Object>(len);
		arrayLength = 0;
		maxNumber = -1;
		Iterator<Object> keys = m.keySet().iterator();
		while (keys.hasNext()) {
			Object key = keys.next();
			if ((key instanceof Integer) || (key instanceof Short) || (key instanceof Byte)) {
				int k = ((Number) key).intValue();
				if (k > -1) {
					arrayLength++;
					if (maxNumber < k) {
						maxNumber = k;
					}
					// assert (maxNumber + 1 >= arrayLength);
				}
				hashMap.put(new Integer(k), m.get(key));
			} else if (key instanceof String) {
				hashMap.put(key, m.get(key));
			}
		}
		setArrayList();
	}

	private void setArrayList() {
		int len = arrayList.size();
		// assert (len <= arrayLength);
		if (len < arrayLength) {
			if (maxNumber + 1 == arrayLength) {
				for (int i = len; i < arrayLength; i++) {
					arrayList.add(hashMap.get(new Integer(i)));
				}
			} else {
				Integer key = new Integer(len);
				while (hashMap.containsKey(key)) {
					arrayList.add(hashMap.get(key));
					key = new Integer(++len);
				}
			}
		}
	}

	public ArrayList<Object> toArrayList() {
		setArrayList();
		return arrayList;
	}

	public HashMap<Object, Object> toHashMap() {
		return hashMap;
	}

	public LinkedHashMap<Object, Object> toLinkedHashMap() {
		return hashMap;
	}

	public int size() {
		return hashMap.size();
	}

	public boolean isEmpty() {
		return hashMap.isEmpty();
	}

	public boolean add(Object element) {
		int index = arrayList.size();
		boolean result = arrayList.add(element);
		if (result) {
			Integer key = new Integer(index);
			if (!hashMap.containsKey(key)) {
				arrayLength++;
				if (maxNumber < index) {
					maxNumber = index;
				}
				// assert (maxNumber + 1 >= arrayLength);
			}
			hashMap.put(key, element);
		}
		return result;
	}

	public boolean addAll(Collection<Object> c) {
		int len = c.size();
		int index = arrayList.size() - 1;
		boolean result = arrayList.addAll(c);
		if (result) {
			for (int i = 0; i < len; i++) {
				Integer key = new Integer(++index);
				if (!hashMap.containsKey(key)) {
					arrayLength++;
				}
				hashMap.put(key, arrayList.get(index));
			}
			if (maxNumber < index) {
				maxNumber = index;
			}
			// assert (maxNumber + 1 >= arrayLength);
		}
		return result;
	}

	public void putAll(Map<Object, Object> m) {
		Iterator<Object> keys = m.keySet().iterator();
		while (keys.hasNext()) {
			Object key = keys.next();
			if ((key instanceof Integer) || (key instanceof Short) || (key instanceof Byte)) {
				int k = ((Number) key).intValue();
				key = new Integer(k);
				if (k > -1 && !hashMap.containsKey(key)) {
					arrayLength++;
					if (maxNumber < k) {
						maxNumber = k;
					}
					// assert (maxNumber + 1 >= arrayLength);
				}
				hashMap.put(key, m.get(key));
			} else if (key instanceof String) {
				hashMap.put(key, m.get(key));
			}
		}
		setArrayList();
	}

	public Object get(int index) {
		if (index < arrayList.size()) {
			return arrayList.get(index);
		} else {
			return hashMap.get(new Integer(index));
		}
	}

	public Object get(Byte key) {
		return get(key.intValue());
	}

	public Object get(Short key) {
		return get(key.intValue());
	}

	public Object get(Integer key) {
		return get(key.intValue());
	}

	public Object get(String key) {
		return hashMap.get(key);
	}

	public Object set(int index, Object element) {
		Integer key = new Integer(index);
		if (index > -1) {
			int size = arrayList.size();
			if (size > index) {
				arrayList.set(index, element);
			} else {
				if (size == index) {
					arrayList.add(element);
				}
				if (!hashMap.containsKey(key)) {
					arrayLength++;
					if (maxNumber < index) {
						maxNumber = index;
					}
					// assert (maxNumber + 1 >= arrayLength);
				}
			}
		}
		return hashMap.put(key, element);
	}

	public Object set(Byte key, Object element) {
		return set(key.intValue(), element);
	}

	public Object set(Short key, Object element) {
		return set(key.intValue(), element);
	}

	public Object set(Integer key, Object element) {
		return set(key.intValue(), element);
	}

	public Object set(String key, Object element) {
		return hashMap.put(key, element);
	}

	public Object remove(int index) {
		Integer key = new Integer(index);
		if (index > -1) {
			if (hashMap.containsKey(key)) {
				arrayLength--;
				int lastIndex = arrayList.size() - 1;
				if (index <= lastIndex) {
					for (int i = lastIndex; i >= index; i--) {
						arrayList.remove(i);
					}
					if (maxNumber == index) {
						maxNumber--;
					}
				} else if (maxNumber == index) {
					while ((--index > lastIndex) && !hashMap.containsKey(new Integer(index)))
						;
					maxNumber = index;
				}
				// assert (maxNumber + 1 >= arrayLength);
			} else {
				return null;
			}
		}
		return hashMap.remove(key);
	}

	public Object remove(Byte key) {
		return remove(key.intValue());
	}

	public Object remove(Short key) {
		return remove(key.intValue());
	}

	public Object remove(Integer key) {
		return remove(key.intValue());
	}

	public Object remove(String key) {
		return hashMap.remove(key);
	}

	public void clear() {
		arrayList.clear();
		hashMap.clear();
		arrayLength = 0;
		maxNumber = -1;
	}

	@SuppressWarnings("unchecked")
	public Object clone() throws CloneNotSupportedException {
		AssocArray result = null;
		result = (AssocArray) super.clone();
		result.arrayList = (ArrayList<Object>) this.arrayList.clone();
		result.hashMap = (LinkedHashMap<Object, Object>) this.hashMap.clone();
		result.arrayLength = this.arrayLength;
		result.maxNumber = this.maxNumber;
		return result;
	}
}